package com.adulcimer.utils;

import com.adulcimer.core.tools.DemonExceptionMessage;
import lombok.extern.slf4j.Slf4j;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoInfo;
import ws.schild.jave.info.VideoSize;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;
import ws.schild.jave.utils.RBufferedReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Simon
 * @create 2025/8/20
 * @Description 针对视频音频的解析处理，需要本地安装ffmpeg，请注意目前maven中没有linux和windows的ffmpeg，请自行安装ffmpeg，并配置环境变量。
 **/
@Slf4j
public class FfmpegFileInfo {
    private static final Pattern SIZE_PATTERN = Pattern.compile("(\\d+)x(\\d+)");
    private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("([\\d.]+)\\s+(?:fps|tbr)");
    private static final Pattern BIT_RATE_PATTERN = Pattern.compile("(\\d+)\\s+kb/s");
    private static final Pattern SAMPLING_RATE_PATTERN = Pattern.compile("(\\d+)\\s+Hz");
    private static final Pattern CHANNELS_PATTERN = Pattern.compile("(mono|stereo|quad)");
    private static final Pattern INPUT_FORMAT_PATTERN = Pattern.compile("^\\s*Input #0, (\\w+).*$");
    private static final Pattern DURATION_PATTERN = Pattern.compile("^\\s*Duration: (\\d\\d):(\\d\\d):(\\d\\d)\\.(\\d\\d).*$");
    private static final Pattern STREAM_PATTERN = Pattern.compile("^\\s*Stream #\\S+: (Audio|Video|Data): (.*)\\s*$");

    private final DefaultFFMPEGLocator locator;
    private File inputFile;
    private URL inputURL;

    public FfmpegFileInfo(File input) {
        this.locator = new DefaultFFMPEGLocator();
        this.inputFile = input;
    }

    public FfmpegFileInfo(URL input) {
        this.locator = new DefaultFFMPEGLocator();
        this.inputURL = input;
    }

    public File getFile() {
        return this.inputFile;
    }

    public URL getURL() {
        return this.inputURL;
    }

    public void setFile(File file) {
        this.inputFile = file;
    }

    public void setURL(URL input) {
        this.inputURL = input;
    }

    public boolean isURL() {
        return this.inputURL != null;
    }

    public FfmpegFileInfo(File input, DefaultFFMPEGLocator locator) {
        this.locator = locator;
        this.inputFile = input;
    }

    public MultimediaInfo getInfo(String url) {
        ProcessWrapper ffmpeg = this.locator.createExecutor();
        ffmpeg.addArgument("-i");
        ffmpeg.addArgument(url);

        try {
            ffmpeg.execute();
        } catch (IOException e) {
            throw new DemonExceptionMessage(e.getMessage());
        }

        try (RBufferedReader reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
            return parseMultimediaInfo(reader);
        } catch (IOException e) {
            throw new DemonExceptionMessage(e.getMessage());
        } finally {
            ffmpeg.destroy();
        }
    }

    private MultimediaInfo parseMultimediaInfo(RBufferedReader reader) {
        MultimediaInfo info = new MultimediaInfo();
        int step = 0;

        try {
            String line;

            while ((line = reader.readLine()) != null) {
                log.debug("Output line: {}", line);
                step = parseMultimediaInfoStatus(step, info, line);
                if (line.startsWith("frame=")) {
                    reader.reinsertLine(line);
                    break;
                }
            }
        } catch (IOException e) {
            throw new DemonExceptionMessage(e.getMessage());
        }
        return info;
    }

    private int parseMultimediaInfoStatus(int step, MultimediaInfo info, String line) {
        Matcher matcher;
        switch (step) {
            case 0:
                if (line.startsWith(inputFile + ": ")) {
                    throw new DemonExceptionMessage(line.substring((inputFile + ": ").length()));
                }
                matcher = INPUT_FORMAT_PATTERN.matcher(line);
                if (matcher.matches()) {
                    info = new MultimediaInfo();
                    info.setFormat(matcher.group(1));
                    step++;
                }
                break;
            case 1:
                matcher = DURATION_PATTERN.matcher(line);
                if (matcher.matches()) {
                    long duration = parseDuration(matcher);
                    info.setDuration(duration);
                    step++;
                }
                break;
            case 2:
                matcher = STREAM_PATTERN.matcher(line);
                if (matcher.matches()) {
                    String type = matcher.group(1);
                    String specs = matcher.group(2);
                    if ("Video".equalsIgnoreCase(type)) {
                        info.setVideo(parseVideoInfo(specs));
                    } else if ("Audio".equalsIgnoreCase(type)) {
                        info.setAudio(parseAudioInfo(specs));
                    }
                }
                break;
            default:
        }
        return step;
    }

    private long parseDuration(Matcher matcher) {
        long hours = Integer.parseInt(matcher.group(1));
        long minutes = Integer.parseInt(matcher.group(2));
        long seconds = Integer.parseInt(matcher.group(3));
        long dec = Integer.parseInt(matcher.group(4));
        return dec * 10L + seconds * 1000L + minutes * 60L * 1000L + hours * 60L * 60L * 1000L;
    }

    private VideoInfo parseVideoInfo(String specs) {
        VideoInfo video = new VideoInfo();
        StringTokenizer st = new StringTokenizer(specs, ",");

        while (st.hasMoreTokens()) {
            String token = st.nextToken().trim();
            if (SIZE_PATTERN.matcher(token).find()) {
                video.setSize(parseVideoSize(token));
            } else if (FRAME_RATE_PATTERN.matcher(token).find()) {
                video.setFrameRate(parseFrameRate(token));
            } else if (BIT_RATE_PATTERN.matcher(token).find()) {
                video.setBitRate(parseBitRate(token));
            }
        }

        return video;
    }

    private VideoSize parseVideoSize(String token) {
        Matcher matcher = SIZE_PATTERN.matcher(token);
        if (matcher.find()) {
            int width = Integer.parseInt(matcher.group(1));
            int height = Integer.parseInt(matcher.group(2));
            return new VideoSize(width, height);
        }
        return null;
    }

    private float parseFrameRate(String token) {
        Matcher matcher = FRAME_RATE_PATTERN.matcher(token);
        if (matcher.find()) {
            try {
                return Float.parseFloat(matcher.group(1));
            } catch (NumberFormatException e) {
                log.info("Invalid frame rate value: {}", matcher.group(1), e);
            }
        }
        return 0;
    }

    private int parseBitRate(String token) {
        Matcher matcher = BIT_RATE_PATTERN.matcher(token);
        if (matcher.find()) {
            return Integer.parseInt(matcher.group(1)) * 1000;
        }
        return 0;
    }

    private AudioInfo parseAudioInfo(String specs) {
        AudioInfo audio = new AudioInfo();
        StringTokenizer st = new StringTokenizer(specs, ",");

        while (st.hasMoreTokens()) {
            String token = st.nextToken().trim();
            if (SAMPLING_RATE_PATTERN.matcher(token).find()) {
                audio.setSamplingRate(parseSamplingRate(token));
            } else if (CHANNELS_PATTERN.matcher(token).find()) {
                audio.setChannels(parseChannels(token));
            } else if (BIT_RATE_PATTERN.matcher(token).find()) {
                audio.setBitRate(parseBitRate(token));
            }
        }

        return audio;
    }

    private int parseSamplingRate(String token) {
        Matcher matcher = SAMPLING_RATE_PATTERN.matcher(token);
        if (matcher.find()) {
            return Integer.parseInt(matcher.group(1));
        }
        return 0;
    }

    private int parseChannels(String token) {
        Matcher matcher = CHANNELS_PATTERN.matcher(token);
        if (matcher.find()) {
            String channels = matcher.group(1);
            return switch (channels.toLowerCase()) {
                case "mono" -> 1;
                case "stereo" -> 2;
                case "quad" -> 4;
                default -> 0;
            };
        }
        return 0;
    }

    public static String getMinSecTime(Long time) {
        long seconds = time % 60;
        long minutes = time / 60;
        return String.format("%02d:%02d", minutes, seconds);
    }
}
